/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package br.com.felinelayer.client.clientSwing;

import br.com.felinelayer.common.Message;
import br.com.felinelayer.common.Protocol;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

/**
 *
 */
public class GridClient extends ComponentSwing<GridClient, JTable> implements br.com.felinelayer.client.IGridClient {

    /** Size of fetch rows read each request */
    private static final int ROWS_FECTH_SIZE = 50;
    /** Swing table/grid */
    private JTable jtable;
    /** Local table model that reads from local buffer */
    private AbstractTableModel localTableModel = null;
    /** Rows buffer */
    private ArrayList<ArrayList<String>> rowsBuffer = new ArrayList<>();
    /** Column count */
    private int columnCount = -1;
    /** Row count */
    private int rowCount = -1;
    /** Column model */
    private TableColumnModel localColumnTableModel;

    /**
     * Return local table model
     *
     * @return table model
     */
    private AbstractTableModel getTableModel() {
        if ( localTableModel != null ) {
            return localTableModel;
        }
        localTableModel = new AbstractTableModel() {
            @Override
            public int getRowCount() {
                return getLocalRowCount();
            }

            @Override
            public int getColumnCount() {
                return getLocalColumnCount();
            }

            @Override
            public Object getValueAt( int rowIndex, int columnIndex ) {
                return getLocalValueCell( rowIndex, columnIndex );
            }
        };
        return localTableModel;
    }

    /**
     * Return cell value from a local buffer or read from server
     *
     * @param rowIndex
     * @param columnIndex
     * @return String
     */
    private String getLocalValueCell( int rowIndex, int columnIndex ) {
        int rs = rowsBuffer.size();
        // While requested row is not in buffer
        while ( rowIndex >= (rowsBuffer.size()) ) {
            int re = rs + ROWS_FECTH_SIZE - 1;
            if ( re >= getLocalRowCount() ) {
                re = getLocalRowCount() - 1;
            }
            // Read cells from server
            ArrayList<ArrayList<String>> cells = getCellsFromServer( rs, 0, re,
                    getLocalColumnCount() - 1 );
            for ( ArrayList<String> cols : cells ) {
                rowsBuffer.add( cols );
            }
            rs = rs + ROWS_FECTH_SIZE;
            if ( rs >= getLocalRowCount() ) {
                rs = getLocalRowCount() - 1;
            }
        }
        return rowsBuffer.get( rowIndex ).get( columnIndex );
    }

    /**
     * Return column count from local cache or server
     *
     * @return column
     */
    private int getLocalColumnCount() {
        if ( columnCount == -1 ) {
            columnCount = getColumnCountFromServer();
        }
        return columnCount;
    }

    /**
     * Return row count from local cache or server
     *
     * @return row
     */
    private int getLocalRowCount() {
        if ( rowCount == -1 ) {
            rowCount = getRowCountFromServer();
        }
        return rowCount;
    }

    /**
     * Return row count from or server
     *
     * @return row
     */
    private int getRowCountFromServer() {
        try {
            Message message = createEvent( Protocol.GRID_GET_ROW_COUNT );
            message = fireInquireEvent( message );
            return message.getIntByName( Protocol.GRID_ROW_COUNT );
        } catch ( Exception ex1 ) {
            Logger.getLogger( GridClient.class.getName() ).log( Level.SEVERE, null, ex1 );
        }
        return 0;
    }

    /**
     * Return column count from or server
     *
     * @return row
     */
    private int getColumnCountFromServer() {
        try {
            Message message = createEvent( Protocol.GRID_GET_COLUMN_COUNT );
            message = fireInquireEvent( message );
            return message.getIntByName( Protocol.GRID_COLUMN_COUNT );
        } catch ( Exception ex ) {
            Logger.getLogger( GridClient.class.getName() ).log( Level.SEVERE, null, ex );
        }
        return 0;
    }

    /**
     * Returns cells values matrix from server side
     *
     * @param sr
     * @param sc
     * @param er
     * @param ec
     * @return cells matrix values
     */
    private ArrayList<ArrayList<String>> getCellsFromServer( int sr, int sc, int er, int ec ) {
        try {
            if ( sr > er ) {
                throw new Exception( "Start row " + sr + " greater than end row " + er );
            }
            // Create message event to server returns cells values
            Message message = createEvent( Protocol.GRID_GET_VALUES_MATRIX );
            // Defines start and end range
            message.setValueByName( Protocol.GRID_ROW_START, sr );
            message.setValueByName( Protocol.GRID_COLUMN_START, sc );
            message.setValueByName( Protocol.GRID_ROW_END, er );
            message.setValueByName( Protocol.GRID_COLUMN_END, ec );
            // Get array values from server
            message = fireInquireEvent( message );
            String[] vetor = message.getValueArray( Protocol.GRID_VALUES_MATRIX );
            // Convert single dimension array to row x column matrix
            ArrayList<ArrayList<String>> result = new ArrayList<>();
            ArrayList<String> linha = null;
            int l = ec - sc;
            int i = 1;
            for ( String m : vetor ) {
                if ( (linha == null) || (i > l) ) {
                    linha = new ArrayList<>();
                    result.add( linha );
                    i = 0;
                }
                linha.add( m );
                i++;
            }
            return result;
        } catch ( Exception ex ) {
            Logger.getLogger( GridClient.class.getName() ).log( Level.SEVERE, null, ex );
        }
        return null;
    }

    /**
     * Returns default column table
     *
     * @return TableColumnModel
     */
    private TableColumnModel getColumnTableModel() {
        if ( localColumnTableModel != null ) {
            return localColumnTableModel;
        }
        localColumnTableModel = new DefaultTableColumnModel();
        return localColumnTableModel;
    }

    @Override
    protected void doInitialize() {
        jtable = new JTable( getTableModel(), getColumnTableModel() );
        JScrollPane scroolPane = new JScrollPane( jtable );
        jtable.setAutoResizeMode( JTable.AUTO_RESIZE_OFF );
        initializeComponente( scroolPane );
    }

    @Override
    public GridClient setText( String text ) {
        //TODO: what to do with setText method?
        return this;
    }

    @Override
    public String getText() {
        //TODO: what to do with setText method?
        return "";
    }

    @Override
    public void messageReceived( Message message ) {
        super.messageReceived( message );
        // here should provide column defs
        // Columns defs message
        if ( (message.getName().equalsIgnoreCase( Protocol.MSG_NAME_COMPONENT_PROPERTY_CHANGE )
                || message.getName().equalsIgnoreCase( Protocol.MSG_NAME_CREATE_COMPONENT ))
                && message.isPropertyExists( Protocol.GRID_GET_COLUMNS ) ) {
            // Extract columns list defs
            List<Message> columns = message.getMessageArray( Protocol.GRID_GET_COLUMNS );
            for ( Message column : columns ) {
                String title = "";
                int index = 0;
                if ( column.isPropertyExists( Protocol.GRID_COLUMN_TITLE ) ) {
                    title = column.getValueByName( Protocol.GRID_COLUMN_TITLE );
                }
                if ( column.isPropertyExists( Protocol.GRID_COLUMN_INDEX ) ) {
                    index = column.getIntByName( Protocol.GRID_COLUMN_INDEX );
                }
                if ( column.isPropertyExists( Protocol.GRID_COLUMN_TYPE ) ) {
                    column.getValueByName( Protocol.GRID_COLUMN_TYPE );
                }
                if ( column.isPropertyExists( Protocol.GRID_COLUMN_ALIGMENT ) ) {
                    column.getValueByName( Protocol.GRID_COLUMN_ALIGMENT );
                }
                // Create column and add to column table modem
                TableColumn col = new TableColumn( index );
                col.setHeaderValue( title );
                getColumnTableModel().addColumn( col );
            }
        }
    }
}
